/*
* Copyright (c) 2011 adVolition
*
* Permission is hereby granted, free of charge, to any person
* obtaining a clone of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* clone, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* Except as contained in this notice, the name(s) of the above
* copyright holders shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without prior
* written authorization.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
 */

package NeuralNetwork;
import java.util.ArrayList;

/**
 * The Brain is a special Edge class with nodes dedicated to motor and sensory input.
 */
public class Brain extends Bundle {

    private transient Mutability _fidelity;

    public Brain() {
        configureObject();
    }

    @Override public Object readResolve() {
        configureObject();
        return this;
    }

    /** Used by class constructor and readResolve routines to ensure that newly created
     * objects are configured properly.
     **/
    private void configureObject() {
        
        if (_parentBundle == null) _parentBundle = this;
        if (_parentFidelity == null) _parentFidelity = new Mutability();
        
        if (_nodes == null) {
            _nodes = new ArrayList<Node>();
        }
        
        if (_edges == null) {
            _edges = new ArrayList<Edge>();
        }

        if (_bundles == null) {
            _bundles = new ArrayList<Bundle>();
        }       
        
        if (_nodes.size() < 1) {
            _nodes.clear();
            _nodes.add(new Node(0.0, this)); // Motors off or on.  Set to 0.8 for on.
            _nodes.add(new Node(0.0, this)); // Linear or rotational motion
            _nodes.add(new Node(0.0, this)); // Direction of motion
            _nodes.add(new Node(0.0, this)); // Sonar input
        }
        
        _edges.add(new Edge(this));
        
        assignParent(this);
    }
   
    /**
     * Used to remove any items that are added to a newly created brain at the start
     * of the cloning process.
     */
    private void clear() {
        _edges.clear();
        _nodes.clear();
        _bundles.clear();
    }
   
   /**
    * This function is generally called by the Robot class to transfer fidelity
    * parameters to the brain and nested bundles.
    * @param fidelity
    */
    public void setFidelity(Mutability fidelity) {
        _fidelity = fidelity;
        assignParent(this);
    }

    /**
     * This is an error-prone method for cloning the object.  It is used to generate mutant robot offspring.
     * @return
     */
    public Brain getClone() {
        Brain newBrain = new Brain();
        newBrain.clear();

        newBrain.setFidelity(_fidelity.getClone());
        
        // Clone nodes.
        int count = 0;
        while (count < _nodes.size()) {
            newBrain.addNode(_nodes.get(count).getClone(newBrain));
            count = nodeSlipCount(count);
        }

        if (Math.random() < _fidelity.getAddNode()) newBrain.addNode(new Node(newBrain));

        // Clone edges.
        count = 0;
        while (count < _edges.size()) {
            newBrain.addEdge(_edges.get(count).getClone(newBrain));
            count = edgeSlipCount(count);
        }

        // Clone bundles.
        count = 0;
        while (count < _bundles.size()) {
            newBrain.addBundle(_bundles.get(count).getClone(newBrain));
            count = bundleSlipCount(count);
        }

        if (Math.random() < _fidelity.getAddBundle()) newBrain.addBundle(new Bundle(newBrain));

        return newBrain;
    }
    
   
    /**
     * Returns the value of the indexed node.  If the node does not exist, it
     * returns a value of 0.0.
     * @param index
     * @return 
     */
    public Double getNodeValue(int index) {
        if (_nodes.size() > index) {
            return _nodes.get(index).getValue();
        }
        return 0.0;
    }

    /**
     * Returns a TRUE value if the robot should be moving backward.
     * @return
     */
    public Boolean moveBackward() {
        if (getNodeValue(0) > 0.5 &&
            getNodeValue(1) < 0.5 &&
            getNodeValue(2) > 0.5) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns a TRUE value if the robot should be moving forward.
     * @return
     */
    public Boolean moveForward() {
        if (getNodeValue(0) > 0.5 &&
            getNodeValue(1) < 0.5 &&
            getNodeValue(2) < 0.5) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * Sets the value of the sonar node.
     * @param value
     */
    public void setSonarNode(Double value) {
        if (_nodes.size() > 3) {
           _nodes.get(3).setValue(value); 
        }        
    }

    /**
     * Causes the brain to process information.
     */
    @Override public void computeFutureState(Double timeIncrement) {
        if (getNodeValue(3) > 0.7) _nodes.get(0).setValue(0.0); // Stops the motor when a wall is near.
    }

    /**
     * Returns a TRUE value if the robot should be turning left.
     * @return
     */
    public Boolean turnLeft() {
        if (getNodeValue(0) > 0.5 &&
            getNodeValue(1) > 0.5 &&
            getNodeValue(2) < 0.5) {
            return true;
        } else {
            return false;
        }                
    }

    /**
     * Returns a TRUE value if the robot should be turning right.
     * @return
     */
    public Boolean turnRight() {
        if (getNodeValue(0) > 0.5 &&
            getNodeValue(1) > 0.5 &&
            getNodeValue(2) > 0.5) {
            return true;
        } else {
            return false;
        }
    }
}
